<?php
// @formatter:off
/**
 * @file WCBasicHolder.abs.php
 * @author Alejandro Dario Simi
 * @date $Date: 2013-06-22 17:58:11 +0000 (Sat, 22 Jun 2013) $
 *
 * $Id: WCBasicHolder.abs.php 83 2013-06-22 17:58:11Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/tags/wcomix-1.0.0.2/includes/holders/WCBasicHolder.abs.php $
 */
// @formatter:on

/**
 * @class WCBasicHolder
 *
 * This is the specification for a generic holder. It holds the logic to manage a
 * list of object, preferable WCTableRepresentation objects indexed by name and
 * id.
 *
 * @warning Every object called item should have two basic properties:
 *  - name
 *  - id
 */
abstract class WCBasicHolder extends WCHolder {
	/**
	 * Constructor is protected because all holders must be singletons.
	 * Therefore, each subclass have to implement a way to create and call the
	 * sole instance. For example, using something like this:
	 * @code
	 * protected static $_Instance;
	 *
	 * public static function Instance() {
	 * 	if(!isset(self::$_Instance)) {
	 * 		$c = __CLASS__;
	 * 		self::$_Instance = new $c();
	 * 	}
	 *
	 * 	return self::$_Instance;
	 * }
	 * @endcode
	 */
	protected function __construct() {
		global $wcDatabaseAccess;
		//
		// Loading database shortcuts.
		// @{
		$this->_db = WCConnection::Instance();
		$this->_dbprefix = $wcDatabaseAccess['prefix'];
		// @}
	}
	/**
	 * Destructor clear all items before deleting.
	 */
	public function __destruct() {
		unset($this->_items);
	}
	/**
	 * Prevent users to clone the instance.
	 */
	public function __clone() {
		trigger_error(__CLASS__.": Clone is not allowed.", E_USER_ERROR);
	}
	//
	// Public methods
	/**
	 * This method allows to create a new item in the database. It also adds
	 * it to the internal list.
	 *
	 * @param string $name Name of the new item to be created.
	 * @return WCTableRepresentation On success returns the new object.
	 * Otherwise returns a null pointer.
	 */
	public function create($name) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// This is the default value in case the item cannot be created.
		$out = null;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Checking if there's no other item with the same name.
			// Unless duplicates are allowed.
			if($this->_PropertiesDupNames || !$this->findItemByName($name)) {
				//
				// Creating the statement to add an item.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__."[{$this->_PropertiesTable}]";
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$this->_dbprefix}$this->_PropertiesTable(\n";
					$query .= "                {$this->_PropertiesPrefix}{$this->_PropertiesNameColumn})\n";
					$query .= "        values (:name)";

					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Inserting the new item into the database.
				$params = array(":name" => $name);
				if($stmt->execute($params)) {
					//
					// If there were no issues creating the
					// item, it is add to the list and
					// returned.
					$out = $this->item($this->_db->lastInsertId());
				}
			}
		}
		//
		// Returning the results of the creation.
		return $out;
	}
	/**
	 * This method return the item with the specified id.
	 *
	 * @param int $id Id of the item to be returned.
	 * @return WCTableRepresentation On success returns the requested object.
	 * Otherwise, it returns false.
	 */
	public function item($id) {
		//
		// Looking for an item with the specified id.
		if($this->findItem($id)) {
			//
			// If it's found, it is return from the list ordered by
			// ids.
			return $this->_items["byid"][$id];
		} else {
			//
			// Otherwise, false.
			return false;
		}
	}
	/**
	 * This method return the item with the specified name.
	 *
	 * @param string $name Name of the item to be returned.
	 * @return WCTableRepresentation On success returns the requested object.
	 * Otherwise, it returns false.
	 *
	 * @todo Bug: when this->_PropertiesDupNames is true, it may fail.
	 */
	public function itemByName($name) {
		//
		// Looking for an item with the specified name.
		if($this->findItemByName($name)) {
			//
			// If it's found, it is return from the list ordered by
			// names.
			return $this->_items["byname"][$name];
		} else {
			//
			// Otherwise, false.
			return false;
		}
	}
	/**
	 * This method returns the list of available ids.
	 *
	 * @return int[] Returns an array of ids.
	 */
	public function itemIds() {
		//
		// By default, it returns an empty array.
		$out = array();
		//
		// Checking if all items are already loaded.
		if(!$this->_fullLoaded) {
			//
			// Preparing the statement to load all ids.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__."[{$this->_PropertiesTable}]";
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  {$this->_PropertiesPrefix}id as id\n";
				$query .= "from    {$this->_dbprefix}{$this->_PropertiesTable}\n";
				$query .= "order by {$this->_PropertiesPrefix}{$this->_PropertiesNameColumn} asc\n";
				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Asking for the ids.
			if($stmt->execute()) {
				//
				// Generating the list of items with the database
				// results.
				foreach($stmt->fetchAll() as $row) {
					$out[] = $row["id"];
				}
			}
		} else {
			//
			// When all items are loaded, the list of ids can be
			// generated from the internal list without using the
			// database again.
			$out = array_keys($this->items());
		}

		return $out;
	}
	/**
	 * This method return the full list of items, all of them fully loaded.
	 *
	 * @return WCTableRepresentation[] Returns an array with all items.
	 */
	public function items() {
		//
		// If this holder already loaded all items, it won't load them
		// again.
		if(!$this->_fullLoaded) {
			//
			// Creating the internal lists to hold all items.
			if(!isset($this->_items["byid"])) {
				//
				// Creating the list ordered by ids.
				$this->_items["byid"] = array();
				//
				// Creating the list ordered by names.
				$this->_items["byname"] = array();
			}
			//
			// Preparing a statement to load all items ids.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__."[{$this->_PropertiesTable}]";
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  {$this->_PropertiesPrefix}id as id\n";
				$query .= "from    {$this->_dbprefix}{$this->_PropertiesTable}\n";
				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Asking for the items.
			if($stmt->execute()) {
				//
				// Loading one by one every item.
				foreach($stmt->fetchAll() as $row) {
					$this->findItem($row["id"]);
				}
				//
				// Setting the flag of fully loaded.
				$this->_fullLoaded = true;
			}
		}
		//
		// Returning the id ordered list.
		return $this->_items["byid"];
	}
	/**
	 * This method clear the internal item's lists and sets the flag of fully
	 * loaded to false.
	 * This method can be used when maybe something in the database was
	 * changed and this holder didn't see it.
	 */
	public function resetCache() {
		unset($this->_items);
		$this->_items = array();
		$this->_fullLoaded = false;
	}
	//
	// Protected Methods
	/**
	 * This method searches for an item using the specified id. If its not
	 * found in the internal lists, it tries to load and add it to its lists.
	 *
	 * @param int $id Name if the item to look for.
	 * @return boolean Returns true when it was found or when it was loaded
	 * and added, and therefore, found.
	 */
	protected function findItem($id) {
		//
		// This is the default value to be returned.
		$out = false;
		//
		// Checking if the item is not yet in the internal lists.
		if(!isset($this->_items["byid"][$id])) {
			//
			// Creating a new not to load the item.
			$item = new $this->_PropertiesItemClass();
			//
			// Loading the item.
			@$item->load($id);
			//
			// Only when the item is okey, it can be hold.
			if($item->ok()) {
				//
				// Adding the loaded item to the internal lists.
				$this->addToCache($item);
				//
				// Returning true.
				$out = true;
			} else {
				//
				// Cleaning used memory on failures.
				unset($item);
			}
		} else {
			//
			// If it is in the internal lists, it returns true.
			$out = true;
		}
		//
		// Return if the item was found or not.
		return $out;
	}
	/**
	 * This method searches for an item using the specified name. If its not
	 * found in the internal lists, it tries to load and add it to its lists.
	 *
	 * @param string $name Name if the item to look for.
	 * @return boolean Returns true when it was found or when it was loaded
	 * and added, and therefore, found.
	 */
	protected function findItemByName($name) {
		//
		// This is the default value to be returned.
		$out = false;
		//
		// Checking if the item is not yet in the internal lists.
		if(!isset($this->_items["byname"][$name])) {
			//
			// Creating a new not to load the item.
			$item = new $this->_PropertiesItemClass();
			//
			// Loading the item.
			@$item->loadByName($name);
			//
			// Only when the item is okey, it can be hold.
			if($item->ok()) {
				//
				// Adding the loaded item to the internal lists.
				$this->addToCache($item);
				//
				// Returning true.
				$out = true;
			} else {
				//
				// Cleaning used memory on failures.
				unset($item);
			}
		} else {
			//
			// If it is in the internal lists, it returns true.
			$out = true;
		}
		//
		// Return if the item was found or not.
		return $out;
	}
}
?>